5. Inflation#

Inflation is the rate at which the general level of prices for goods and sevices changes.

Hide code cell source
# Load libraries
import numpy as np
import pandas as pd

import plotly.graph_objects as go
import plotly.express as px

import requests
from io import BytesIO
Hide code cell source
# Import data
folder = 'https://raw.githubusercontent.com/Basics2022/bbooks-financial-edu/master/code/data/'

#> Files
# FOI-prices: 2016-.../2025-...
# IPCA-weights: 2018/2025
# IPCA-prices:  2018-.../2025-...
# NIC-weights: 2018/2025
# NIC-prices:  2018-.../2025-...
filen = {
    'FOI-prices'  : folder+'monthly-FOI.xlsx',  # folder+'monthly-FOI.xlsx' ,
    'IPCA-weights': folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_6_DF_DCSP_IPCA3_1%2C1.0).xlsx',        # folder+'Classificazione Ecoicop (4 cifre) (IT1,168_6_DF_DCSP_IPCA3_1,1.0).xlsx',
    'IPCA-prices' : folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_760_DF_DCSP_IPCA1B2015_1%2C1.0).xlsx', #folder+'Classificazione Ecoicop (4 cifre) (IT1,168_760_DF_DCSP_IPCA1B2015_1,1.0).xlsx',
    'NIC-weights' : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_743_DF_DCSP_NIC3B2015_3%2C1.0).xlsx',  # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_743_DF_DCSP_NIC3B2015_3,1.0).xlsx',
    'NIC-prices'  : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_744_DF_DCSP_NIC1B2015_4%2C1.0).xlsx' # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_744_DF_DCSP_NIC1B2015_4,1.0).xlsx'
}
Hide code cell source
df_ipca = {
    'prices' : pd.read_excel(filen['IPCA-prices'], sheet_name='data', decimal=',', engine='openpyxl'),
    'weights': pd.read_excel(filen['IPCA-weights'], sheet_name='data', decimal=',', engine='openpyxl')
}

for kdf, idf in df_ipca.items():
    idf.columns = idf.columns.str.strip()  # strip whitespaces
    idf = idf.set_index(['Tempo'])
    idf.index = idf.index.str.strip()
    idf = idf.transpose()
    idf = idf.rename(columns={'index': 'Tempo'})

5.1. Inflation Indices (e.g. in Italy)#

Overall inflation is the the weighted average of inflation on different classes of goods and services, weighted for their share of expenses.

Everyone perceives its own inflation, depending on its expenses. Different indices are usually used within an economy to track inflation for some “average individual”.

Different indices may differ on values of weights, and other “details” like the effect of discounts and public transfers.

As an example, three indices are used in Italy:

  • NIC (Prezzi al Consumo per l’intera Collettività Nazionale), usually the general

  • FOI (Prezzi al Consumo per Famiglie di Operai e Impiegati), usually used for contracts, pension and inflation-linked contracts, ex-tobacco and lotteries.

  • IPCA (Indice Armonizzato dei Prezzi al Consumo, HIPC Harmonized Index of Consumer Prices), used for comparison and statistics in the EU

Hide code cell source
# Compare IPCA, NIC and FOI
# ...

5.2. Weights and Price Indices of Classes of Goods and Services - Italy IPCA#

National and International Institutions for Statistics (in Italy, ISTAT) provide open-access databases collecting statistics about society and economics, including data about price.

ISTAT. As an example, Italian ISTAT provides data at https://esploradati.istat.it/databrowser/#/it/dw

All the data we need here is available under the category “Prezzi” - Prices. In order to reach a reasonable stability of the notebook, data have been downloaded, cleaned and stored in a folder on the repository of the project.

5.2.1. Inspect Data#

Before producing plots, price indices and weights of level-4 categories are visually inspected. Data are usually collected in tables.

5.2.1.1. Price Indices - Level-4 IPCA categories#

Hide code cell source
df_ipca['prices']
Tempo 2018-01 2018-02 2018-03 2018-04 2018-05 2018-06 2018-07 2018-08 2018-09 ... 2024-09 2024-10 2024-11 2024-12 2025-01 2025-02 2025-03 2025-04 2025-05 2025-06
0 [00] Indice generale 100.6 100.1 102.4 102.9 103.2 103.4 102 101.8 103.5 ... 123 123.4 123.3 123.4 122.4 122.5 124.4 124.9 124.8 125
1 [01] -- prodotti alimentari e bevande analcoli... 103.9 103 103.2 103.6 104.3 103.9 103.1 103.1 103 ... 130.8 132.3 133.3 132.6 133.8 133.7 133.8 134.8 135.4 135.7
2 [011] Prodotti alimentari 104 103.2 103.4 103.7 104.5 104.2 103.2 103.2 103.2 ... 131.2 132.9 133.8 133 134.1 134 134 134.9 135.5 ..
3 [0111] Pane e cereali 101.6 100.6 101.1 101.8 101.4 102 101.7 102.1 101.3 ... 127.2 127.6 127.8 127.8 128.5 128.2 128.4 129.2 129.3 ..
4 [0112] Carni 102.9 102.4 102.6 102.9 102.7 102.8 102.8 102.7 103 ... 125.6 125.9 126.7 127 127.9 128 128.7 129.5 130.2 ..
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
145 [SERVTRANS_5DG] Servizi relativi ai trasporti ... 101.1 102.6 104.4 104.5 104.7 107 107.6 112.6 107 ... 123 123 122.8 124.3 121.8 121.5 122.9 127.1 125 ..
146 [SERVMISC_5DG] Servizi vari (dettaglio 5-digit) 99.8 100.1 100.3 100.6 101 101.1 101.3 101.3 101.4 ... 112.4 112.9 113 113 113.3 113.5 113.8 113.9 114 ..
147 [00XEFOODUNP_5DG] Componente di fondo (core in... 99.9 99.5 102.2 103 103.1 103.3 101.5 101.3 103.2 ... 117.9 118 117.8 117.9 116.1 116.1 118.2 119.7 119.9 120.4
148 [00XEFOOD_5DG] Indice generale al netto dell'e... 99.4 99 102.2 103 103.1 103.4 101.1 100.9 103.2 ... 115.8 116 115.6 115.8 113.5 113.3 115.9 117.6 117.7 118.1
149 [00XE_5DG] Indice generale esclusi energetici ... 100.4 99.9 102.4 103.2 103.4 103.6 101.7 101.5 103.3 ... 119 119.4 119.2 119.3 117.7 117.6 119.7 121.2 121.4 121.8

150 rows × 91 columns

5.2.1.2. Price Indices - Level-4 IPCA categories#

Hide code cell source
df_ipca['weights']
Tempo 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
0 [00] Indice generale 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000 1000000
1 [01] -- prodotti alimentari e bevande analcoli... 175648 176326 175240 175418 173257 172583 205912 194554 181443 181801 181425
2 [011] Prodotti alimentari 162005 162805 161810 161903 159432 158644 189091 179008 166582 167112 166336
3 [0111] Pane e cereali 30036 30342 29853 29558 29717 29778 35767 33586 31994 31422 31513
4 [0112] Carni 41803 40944 40876 39914 39286 38162 45695 43159 39770 40253 40080
... ... ... ... ... ... ... ... ... ... ... ... ...
144 [1252] Assicurazione connessa all'abitazione .. .. 1102 456 469 398 525 466 470 517 487
145 [1253] Servizi assicurativi connessi alla salu... 467 584 530 560 605 720 903 781 728 748 692
146 [1254] Assicurazioni sui mezzi di trasporto 13095 13767 13191 12641 12033 12020 14649 12737 12773 13259 12486
147 [126] Servizi finanziari n.a.c. 13060 14353 13628 12614 15124 15559 17041 14343 14862 14946 15195
148 [127] Altri servizi n.a.c. 19820 19403 19046 19833 21045 21951 18066 20537 19186 19297 20422

149 rows × 12 columns

Hide code cell source
# Useful new dataframe to match weights (yearly) and prices (monthly) later
#> Extract code and label
ddf = {}

ddf = pd.DataFrame()
ddf['code' ] = df_ipca['weights']['Tempo'].str.extract(r'(\[\d+\])')
ddf['label'] = df_ipca['weights']['Tempo'].str.strip()

#> Determine parent code
def get_parent_code(code):
    if code is None or pd.isna(code):
        return None
    code_str = str(code).strip('[]')
    if len(code_str) <= 2:
        return None  # no parent
    elif len(code_str) == 3:
        return f"[{code_str[:2]}]"
    elif len(code_str) == 4:
        return f"[{code_str[:3]}]"
    else:
        return None

#> Determine value
def get_value(code):
    if code is None or pd.isna(code):
        return None
    code_str = str(code).strip('[]')
    if len(code_str) == 4:
        return int(1)
    else:
        return int(0)

#> Determine parents and values
ddf['parent'] = ddf['code'].map( get_parent_code )

years = [str(y) for y in range(2018, 2026)]  # list of year strings
for year in years:
   ddf[str(year)] = df_ipca['weights'][str(year)] / 1e4

#> Drop [00] Indice generale
ddf = ddf[ddf['code'] != '[00]']

# ddf.head(5)

5.2.2. Plots#

5.2.2.1. Level-2 IPCA Category weights#

Hide code cell source
import plotly.graph_objects as go

# Create the initial figure for the first year
fig = go.Figure()

fig.add_trace(go.Sunburst(
    ids=ddf['code'],
    labels=ddf['code'],
    parents=ddf['parent'],
    values=ddf['2025'],  # initial year
    hoverinfo='label+value+text',
    branchvalues="total",  # "toatal" or "remainder"
    sort=False,
    text=ddf['label'],
))
fig.update_layout(title="IPCA weights - 2025")

# Create one frame per year
frames = []
for year in years:
    frames.append(go.Frame(
        data=[go.Sunburst(
            ids=ddf['code'],
            labels=ddf['code'],
            parents=ddf['parent'],
            values=ddf[year],
            branchvalues="total",  # "toatal" or "remainder"
            sort=False,
            text=ddf['label']
        )],
        name=year,
        layout=go.Layout(title_text=f"IPCA weights - {year}")
    ))

fig.frames = frames

# Add slider steps for each year
steps = []
for year in years:
    steps.append(dict(
        method='animate',
        args=[[year],  # frame name
              dict(mode='immediate',
                   frame=dict(duration=500, redraw=True),
                   transition=dict(duration=300))],
        label=year
    ))

# Layout with slider
fig.update_layout(
    # width=800, height=800,
    margin=dict(t=50, l=0, r=0, b=0),
    sliders=[dict(
        active=years.index('2025'),
        currentvalue={"prefix": "Year: "},
        pad={"t": 50},
        steps=steps
    )],
)

fig.show()

5.2.2.2. Level-2 IPCA Category Prices#

Hide code cell source
#> Classes
class_names = ddf[ ddf['parent'].isnull() ]['label'].tolist()

df_ipca['prices']['Tempo'] = df_ipca['prices']['Tempo'].str.strip()

# Add a trace for each row (now a column in df_T)
# df_classes = df['prices'][ df['prices']['Tempo'].isin(class_names) ]
df_classes = df_ipca['prices'][df_ipca['prices']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose()

# Create a figure
fig = go.Figure()

df_classes

for column in df_classes.columns:
    fig.add_trace(go.Scatter(
        x=df_classes.index,
        y=df_classes[column],
        mode='lines',
        name=column[:50]
    ))

# Customize layout
fig.update_layout(
    title='IPCA price indices - 2015:100',
    xaxis_title='Time',
    yaxis_title='Value',
    # xaxis_tickangle=-45,
    hovermode='x unified',
    # template='plotly_white',
    height=600,
    width=1000,
    # legend=dict(orientation="v", x=1.02, y=1)
)

fig.show()

5.2.2.3. Level-2 IPCA Category Price Changes - Inflation#

Hide code cell source
df_inflation = df_classes.pct_change(periods=12).dropna(how='any')   # percent

# Create a figure
fig = go.Figure()

for column in df_inflation.columns:
    fig.add_trace(go.Scatter(
        x=df_inflation.index,
        y=df_inflation[column],
        mode='lines',
        name=column[:50]
    ))

# Customize layout
fig.update_layout(
    title='IPCA inflation by categories - 12 months price difference',
    xaxis_title='Time',
    yaxis_title='Value',
    # xaxis_tickangle=-45,
    hovermode='x unified',
    # template='plotly_white',
    height=600,
    width=1000,
    # legend=dict(orientation="v", x=1.02, y=1)
)

fig.show()

5.2.2.4. Category contributions to overall inflation#

Hide code cell source
#> Need for aligning data with different time intevals

df_inflation.index = pd.to_datetime(df_inflation.index)
df_inflation.index = df_inflation.index.to_period('M')
# df_inflation


df_weights = df_ipca['weights'][ df_ipca['weights']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose() / 1e6
#> Add a row 2026, just to use the same weights for all the months of 2025
row_2026 = df_weights.loc[['2025']].copy()
row_2026.index = ['2026']
df_weights = pd.concat([df_weights, row_2026],)


df_weights.index = pd.to_datetime(df_weights.index)
df_weights = df_weights.asfreq('YS')
# # df_weights_monthly = df_weights
df_weights = df_weights.resample('MS').ffill()
df_weights.index = df_weights.index.to_period('M')

# print(df_weights.index)
# print(df_inflation.index)


common_periods = df_inflation.index.intersection(df_weights.index)
df_monthly_weights = df_weights.loc[common_periods]


# df_monthly_weights
# df_inflation


df_monthly_weights.columns = df_monthly_weights.columns.str.strip()
df_inflation.columns = df_inflation.columns.str.strip()

df_contributions = df_monthly_weights * df_inflation

df_contributions = df_contributions.drop('[00] Indice generale', axis=1)
df_contributions.index = df_contributions.index.to_timestamp()

# df_contributions
Hide code cell source
fig = go.Figure()

# df_contributions = df_contributions.dropna(axis=1, how='all')

for col in df_contributions.columns:
    fig.add_trace(go.Scatter(
        x=df_contributions.index,
        y=df_contributions[col],
        mode='lines',
        stackgroup='one',  # enables stacking
        name=col
    ))

fig.update_layout(
    title="Contributions to Year-over-Year Inflation (Weighted by IPCA)",
    xaxis_title="Date",
    yaxis_title="Contribution to Inflation (%)",
    # width=1000,
    # height=600
)

fig.show()
Hide code cell source
# df_plot = df_contributions.reset_index()
df_plot = df_contributions.reset_index().rename(columns={'Tempo': 'Time'})

# df_plot['Tempo'] = pd.to_datetime(df_plot['Tempo'])
df_plot.index = pd.to_datetime(df_plot.index)
df_plot

# print(df_plot.columns.to_list())
# # print(df_plot.head())
# print(df_plot.dtypes)

# Create a new DataFrame by resetting the index and renaming the index column to 'Time'
df_new = df_contributions.reset_index()

# If the old index had no name, the column will be called 'index', so rename it:
if 'index' in df_new.columns:
    df_new = df_new.rename(columns={'index': 'Time'})
else:
    # If it has a name, replace that with 'Time'
    old_name = df_contributions.index.name
    if old_name is not None and old_name in df_new.columns:
        df_new = df_new.rename(columns={old_name: 'Time'})
    else:
        # If the index has no name and no 'index' column, just add one:
        df_new['Time'] = df_contributions.index

# Now df_new has a clean 'Time' column and a fresh integer index
# print(df_new.head())

df_new
Tempo Time [01] -- prodotti alimentari e bevande analcoliche [02] -- bevande alcoliche e tabacchi [03] -- abbigliamento e calzature [04] -- abitazione, acqua, elettricità, gas e altri combustibili [05] -- mobili, articoli e servizi per la casa [06] -- servizi sanitari e spese per la salute [07] -- trasporti [08] -- comunicazioni [09] -- ricreazione, spettacoli e cultura [10] -- istruzione [11] -- servizi ricettivi e di ristorazione [12] -- altri beni e servizi
0 2019-01-01 0.000834 0.000754 -0.000187 0.004464 0.0 0.000341 0.001504 -0.001614 -0.000238 0.0 0.001366 0.001651
1 2019-02-01 0.003028 0.001132 -0.000199 0.00446 0.000154 0.00034 0.000895 -0.001936 -0.000178 0.0 0.001114 0.001749
2 2019-03-01 0.001679 0.001058 0.00023 0.00446 -0.000076 0.000255 0.001778 -0.001873 -0.00012 0.000013 0.001106 0.001636
3 2019-04-01 0.000334 0.000772 0.000148 0.003974 -0.000076 0.000297 0.00399 -0.002419 -0.00012 0.000013 0.001935 0.001916
4 2019-05-01 0.000664 0.000676 0.000222 0.003861 0.0 0.000297 0.002936 -0.002491 -0.00006 0.000013 0.00132 0.00153
... ... ... ... ... ... ... ... ... ... ... ... ... ...
73 2025-02-01 0.004449 0.00079 -0.000433 0.003935 0.000513 0.001076 -0.00013 -0.001019 0.001017 0.000292 0.003506 0.002718
74 2025-03-01 0.00473 0.001011 0.000905 0.007548 0.000448 0.001188 -0.00142 -0.000944 0.001073 0.000292 0.004101 0.002699
75 2025-04-01 0.005978 0.000567 0.000458 0.005752 0.000318 0.001146 -0.001411 -0.000976 0.00075 0.000292 0.00492 0.002683
76 2025-05-01 0.005951 0.000621 0.000516 0.004934 0.000446 0.001183 -0.003094 -0.000877 0.00053 0.000292 0.004356 0.002681
77 2025-06-01 0.006797 0.000702 0.000458 0.002547 0.000509 0.001257 -0.001422 -0.000852 0.000584 0.000292 0.004312 0.002762

78 rows × 13 columns

Hide code cell source
y_cols = [col for col in df_new.columns if col != 'Time']

df_long = df_new.melt(id_vars='Time', value_vars=y_cols, var_name='Category', value_name='Value')

fig = px.bar(
    df_long,
    x='Time', # 'x',
    y='Value',
    color='Category',
    title='Stacked bars with relative mode'
)

fig.update_layout(barmode='stack') # relative')
fig.show()


# df_contributions

5.3. Correlations in macroeconomics with inflation#

Some correlations exist1 between inflation and other macroeconocmics quantitites.

  • Phillips Curve: inverse relation between inflation and unemployment (in the short-run)

  • Money supply in the long-run “Inflation is a monetary phenomenon”, M.Friedman.

5.4. Control of Inflation#

Control of inflation is one of the goals of central banks, like the FED and the ECB.

Central banks aims at controlling inflation, matching target inflation (usaully set as 2%) by means of monetary policy:

  • interest rates (cost of money)

  • non-conventional actions, like quantitative easing (QE)/tightening (QT)

A goverment may indirectly influence inflation with fiscal policy, as taxation and government spending can influence demand.

Credibility of targets, and actors through their actions and forward guidance may influence inflation as well: expectations influences inflation.


1